package io.gitee.dtdage.app.boot.starter.data.mybatis.utils;

import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.SneakyThrows;
import org.springframework.cglib.core.ReflectUtils;

import java.beans.PropertyDescriptor;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @author WFT
 * @since 2024/4/14
 */
public class LambdaUtil {

    private LambdaUtil() {

    }

    /**
     * 以静态内部类实例化当前对象,从而达到懒汉式单例的效果
     */
    private static class Holder {

        private final static LambdaUtil INSTANCE = new LambdaUtil();

        private final static Map<Class<?>, PropertyDescriptor[]> MAP = new HashMap<>(16);

    }

    /**
     * 获取当前实例
     *
     * @return {@link LambdaUtil}
     */
    public static LambdaUtil getInstance() {
        return Holder.INSTANCE;
    }

    @SneakyThrows
    public <T> SFunction<T, ?> getter(Class<T> clazz, String attribute) {
        //  通过反射获取类中的所有Getter方法描述器
        PropertyDescriptor[] descriptors = Optional.ofNullable(Holder.MAP.get(clazz)).orElseGet(() -> {
            Holder.MAP.put(clazz, ReflectUtils.getBeanGetters(clazz));
            return Holder.MAP.get(clazz);
        });
        //  获取指定字段的Getter方法
        Method method = Arrays.stream(descriptors)
                .filter(item -> item.getName().equals(attribute))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("字段不存在!"))
                .getReadMethod();
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        //noinspection unchecked
        return (SFunction<T, ?>) LambdaMetafactory.altMetafactory(
                lookup,
                "apply",
                MethodType.methodType(SFunction.class),
                MethodType.methodType(Object.class, Object.class),
                lookup.unreflect(method),
                MethodType.methodType(method.getReturnType(), clazz),
                LambdaMetafactory.FLAG_SERIALIZABLE
        ).getTarget().invokeExact();
    }

}
